Wire eth_subscribe(newHeads) for Autobahn (CON-257)#3419
Conversation
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3419 +/- ##
==========================================
+ Coverage 59.34% 59.35% +0.01%
==========================================
Files 2112 2113 +1
Lines 174772 174863 +91
==========================================
+ Hits 103724 103796 +72
- Misses 62010 62027 +17
- Partials 9038 9040 +2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
encodeCommittedBlock was mirroring the legacy encodeTmHeader, which emits both the typo'd "withdrawlsRoot" and the correctly-spelled "withdrawalsRoot". Since the Autobahn encoder is new code there is no back-compat reason to carry the typo; legacy encodeTmHeader is left untouched. Reported by Cursor Bugbot on #3419. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ResponseFinalizeBlock.ConsensusParamUpdates is only populated when the app proposes a consensus-param update — nil on the vast majority of blocks. Reading it for newHeads gasLimit means the reported gasLimit would be 0 for nearly every notification under Autobahn. Match the pattern already used in evmrpc/block.go's GetBlockByNumber: pull gasLimit from ctx.ConsensusParams() in the consumer goroutine and pass it as a parameter into encodeCommittedBlock. Reported by Cursor Bugbot on #3419 (high severity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 84cd2e6. Configure here.
Under Autobahn, app.FinalizeBlock is driven by giga_router rather than
CometBFT consensus, so the legacy Tendermint event-bus subscription
that backs eth_subscribe("newHeads") never fires. This adds a direct
in-process notifier from giga_router's commit path into evmrpc's
SubscriptionAPI fan-out (overwrite-on-full, capacity 1, so the latest
head always wins if a consumer lags). The legacy path is untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
encodeCommittedBlock was mirroring the legacy encodeTmHeader, which emits both the typo'd "withdrawlsRoot" and the correctly-spelled "withdrawalsRoot". Since the Autobahn encoder is new code there is no back-compat reason to carry the typo; legacy encodeTmHeader is left untouched. Reported by Cursor Bugbot on #3419. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tighten hash field doc in blockHeaderEvent: receipt store records zero blockHash on disk and evmrpc overlays the autobahn hash at read time. The prior wording implied the receipt store stored it directly. - Clarify single-producer assumption in OnBlockCommitted and document multi-producer behaviour (still safe, latest-wins is acceptable). - Move SubscriptionManager construction inside the legacy event-bus branch in NewSubscriptionAPI so the field is nil under Autobahn instead of dead state. - Document the non-nil header/response contract on BlockHeaderListener, and add a defensive guard in runNewHeadsFromNotifier so a single malformed event does not kill the fan-out goroutine for all subscribers. - Annotate the listener call site in giga_router.executeBlock so the fire-after-Commit-before-mempool-update ordering is explicit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ResponseFinalizeBlock.ConsensusParamUpdates is only populated when the app proposes a consensus-param update — nil on the vast majority of blocks. Reading it for newHeads gasLimit means the reported gasLimit would be 0 for nearly every notification under Autobahn. Match the pattern already used in evmrpc/block.go's GetBlockByNumber: pull gasLimit from ctx.ConsensusParams() in the consumer goroutine and pass it as a parameter into encodeCommittedBlock. Reported by Cursor Bugbot on #3419 (high severity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GetNextBaseFeePerGas(ctx_at_N) returns the base fee for block N+1, not block N. The previous code passed the current block's ctx, so the newHeads notification for block N reported what should have been attached to N+1. Match the pattern from block.go's GetBlockByNumber: derive baseFee from the parent block's ctx, with a genesis (height==1) fallback to DefaultMinFeePerGas. The legacy CometBFT path has the same bug but is left alone — the more important consistency to restore is between eth_subscribe(newHeads) and eth_getBlockByNumber on the same Autobahn cluster. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ExecTxResult has both a GasWanted and a GasUsed field with different semantics. The variable was accumulating GasUsed but named "gasWanted", inviting a future field-swap regression. Rename for clarity. Legacy encodeTmHeader carries the same anti-pattern but is left alone. Reported by Cursor Bugbot on #3419 (low severity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
073496b to
c8c0d46
Compare
|
|
||
| // First message must be the subscription confirmation. Bound the wait | ||
| // so a broken handshake fails fast. | ||
| if err := conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { |
There was a problem hiding this comment.
This err shadows definition of err on line 42. Can just reuse it (similarly in other lines):
if err = conn.SetReadDeadline....
The five `if err := ...` blocks in TestEthSubscribeNewHeads each shadowed the outer err from the initial Dial call. Reuse with `err =` for clarity (the outer err is already in scope and gets overwritten on each call anyway). Reported by @amir-deris on #3419. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The base-fee selection in runNewHeadsFromNotifier was the actual location of the off-by-one fix (parent ctx vs current ctx) but had no direct test coverage — encodeCommittedBlock takes baseFee as an arg, so testing it only proved the encoder forwards the value it's given, not that the caller picks the right ctx. Extract pickHeadBaseFee as a free function that takes getNextBaseFee as a function pointer (rather than reaching into *keeper.Keeper), then spy on the ctxProvider in tests to verify: - TestPickHeadBaseFee_UsesParentCtx: ctxProvider called with height-1 (NOT height), and result is forwarded. - TestPickHeadBaseFee_GenesisFallback: at height 1 the keeper call is skipped entirely and DefaultMinFeePerGas is returned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Summary
eth_subscribe("newHeads")is currently broken: the legacy fan-out subscribes to the Tendermint event bus ontm.event = 'NewBlockHeader', but Autobahn drivesapp.FinalizeBlockfromgiga_routerrather than CometBFT consensus, soPublishEventNewBlockHeadernever fires.giga_router's post-commit point intoevmrpc.SubscriptionAPI's fan-out goroutine, scoped strictly to Autobahn mode (notifier is only constructed whentmConfig.AutobahnConfigFile != ""). The legacy CometBFT path is untouched.newHeadssubscribers care about the latest head, not a backlog, so stale heads are dropped in favor of new ones.Design notes
A simpler alternative was to keep using the event bus and just have
giga_routercalleventBus.PublishEventNewBlockHeader(...)after commit. That would have been a ~20-line diff with zero evmrpc changes (the existing consumer would just work). We opted not to because (1) the pubsub layer isn't doing anything load-bearing for this use case — one fixed query, one subscriber — and (2) we plan to drop the cosmos event bus entirely once external consumers move offtm.event='Tx'and friends. A direct channel is the end-state design; this PR lands it now to avoid carrying the stopgap.hashon the published header is the autobahn lane-block header hash (the same value the EVM receipt store records asblockHash), not a hash over the synthesized Tendermint header.parentHash/receiptsRoot/transactionsRootare zero under Autobahn — the lane-block path doesn't compute a Tendermint-style hash chain, and surfacing fakes would be worse than zeros. Subscribers that chain-validate the head stream will need a different mechanism going forward; this is called out in the encoder's doc.Test plan
gofmt -s -lclean on all modified files;go build ./...clean.go test ./evmrpc/— 249 PASS, 8 SKIP, 0 FAIL (22s). Includes:TestSubscribeNewHeads(legacy event-bus path) — unchanged behaviour.TestSubscribeNewHeadsAutobahn(notifier path, in-process WS server end-to-end).BlockHeaderNotifier(deliver, overwrite-on-full, nil-safe) andencodeCommittedBlock(happy path + nilConsensusParamUpdates).evm_rpc_tests.sh(HTTP-method fixtures) against a real non-Autobahn cluster — 160/160 pass, no regressions on the request/response RPC surface.integration_test/evm_module/ws_test/— consensus-mode-agnostic, gated onSEI_EVM_WS_RUN_INTEGRATION=1, wired intoevm_rpc_tests.sh. Run end-to-end against both cluster modes:number=0x2b, hash0xe0dd089b...)number=0xf5, hash0xc4d1bc59...,GigaRouter initializedconfirmed in all 4 node logs)non-app-hash-breakinglabel is correct — this PR only adds a notification side-effect after commit; no state machine or app-hash-relevant logic changes.🤖 Generated with Claude Code
Note
Medium Risk
Adds a new post-commit callback on Autobahn’s block execution hot path and changes
eth_subscribe("newHeads")delivery to use an in-process channel when enabled; mistakes could drop notifications or affect performance, though legacy CometBFT behavior is preserved.Overview
Fixes
eth_subscribe("newHeads")under Autobahn by bypassing the Tendermint event bus and instead pushing committed block headers through a new in-processBlockHeaderNotifier.giga_routernow invokes an optionaltypes.BlockHeaderListenerafter successfulCommit, the app wires this to the notifier only when Autobahn is enabled, andevmrpc.SubscriptionAPIswitches between the legacy event-bus subscription and the notifier-fed fanout. The Autobahn encoder uses the Autobahn-provided block hash, derivesgasUsedfrom tx results, readsgasLimitfrom current consensus params, and fixesbaseFeePerGasselection to use the parent height.Adds unit + WS end-to-end tests for notifier semantics (overwrite-on-full) and Autobahn newHeads delivery, plus a gated integration WS test and script updates to run it.
Reviewed by Cursor Bugbot for commit 1cd5600. Bugbot is set up for automated code reviews on this repo. Configure here.